package cn.ly.lock;

import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;

/**
 * 方式一
 * 使用Zookeeper实现一个分布式锁
 * 使用注意：每个线程中都单独new 一个 ZkLock。线程持有自己的ZkLock对象，然后去操作Zk服务器的节点
 * 区别于synchronized对象锁、jdk的Lock锁。所有线程都是在同一个对象锁上排队、阻塞等待
 */
public class ZkLock extends AbstractLock {
    //ZK服务器连接地址
    private static final String CONNECT_IP="127.0.0.1:2181";
    //创建ZK连接：【注意】：在zoo.cfg配置文件中：记得修改：maxClientCnxns=60。表示同一ip可以并发的最大连接数。需要调大一些
    private ZkClient zkClient = new ZkClient(CONNECT_IP);
    private static final String LOCK_NODE = "/lock";

    @Override
    public boolean tryLock() {
        try{
            //创建临时节点，防止死锁
            zkClient.createEphemeral(LOCK_NODE);
            System.out.println(">>>>>>>>>>获取到锁"+Thread.currentThread().getName());
            return true;
        }catch (Exception e){
            //创建失败，即没有抢到锁
            return false;
        }
    }

    //一个对象锁
    private final Object obj = new Object();
    //没有获取到锁的线程等待
    public void waitLock() {
//         CountDownLatch countDownLatch = new CountDownLatch(1);
        //事件
        IZkDataListener listener = new IZkDataListener() {
            //节点被删除时，触发的事件（即其他线程删除节点：释放锁了）
            @Override
            public void handleDataDeleted(String s) throws Exception {
//              countDownLatch.countDown(); //唤醒正等待着的线程
                synchronized (obj){     //也可以使用对象锁，唤醒
                    obj.notifyAll();
                }
            }
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
            }
        };
        //注册事件
        zkClient.subscribeDataChanges(LOCK_NODE,listener);

        //线程阻塞（即节点存在，其他线程已经持有锁了。本线程需要阻塞等待通知）
        if(zkClient.exists(LOCK_NODE)){
//            countDownLatch= new CountDownLatch(1);
            try{
//                countDownLatch.await(); //阻塞
                synchronized (obj){
                    obj.wait();
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
        System.out.println(">>>>>>>线程被唤醒"+Thread.currentThread().getName());
        //被唤醒后，删除该监听器（不需一直监听了，该去抢锁了）
        zkClient.unsubscribeDataChanges(LOCK_NODE,listener);
    }

    //释放锁，即删除该节点
    @Override
    public void unLock() {
        if(zkClient!=null){
            System.out.println(">>>>>>>>准备释放锁资源"+Thread.currentThread().getName());
            zkClient.delete(LOCK_NODE); //上面创建的临时节点，故就算此zkClient断开了，锁也能自动释放
            zkClient.close();   //Exception in thread "Thread-18" java.lang.IllegalStateException: ZkClient already closed!
        }
    }

}
